package core

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"math"
	"math/big"
)

//定义难度值
const (
	targetBits = 20
)

//定义一个最大值，用于计数器的计数，这个值是整数64位里的最大值
var maxNonce = math.MaxInt64

//ProofOfWork 工作量证明结构体，包含区块和目标
type ProofOfWork struct {
	block  *Block
	target *big.Int
}

/*
整个流程说白了就是先初始化一个值
然后用区块的数据进行hash后与这个值比较
如果小于这个值就证明算出来了，就可以创建区块
*/

//NewProofOfWork 创建工作量证明
func NewProofOfWork(b *Block) *ProofOfWork {
	//目标给个整数1
	target := big.NewInt(1)
	//对target进行移位操作，最终出来的就是目标值
	target.Lsh(target, uint(256-targetBits))
	pow := &ProofOfWork{b, target}
	return pow
}

//prepareData 用于将区块数据和难度值及计数器合并为一个[]byte，返回的数据用于hash计算
func (pow *ProofOfWork) prepareData(nonce int) []byte {
	//其实就是将这些数据合并为一个
	/*
		这里说一下IntToHex，这就是用来将int类型转换为[]byte类型，是原作者自己写的
		详情看utils文件里的IntToHex函数
	*/
	data := bytes.Join([][]byte{pow.block.PrevBlockHash, pow.block.Data, IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce))}, []byte{})
	//其实我觉得这里直接返回hash也行，更好理解
	return data
}

//Run 这个就是工作量计算的核心函数
func (pow *ProofOfWork) Run() (int, []byte) {
	//定义一个大整数，用来装hash值的int类型，用于和目标值比较
	var hashInt big.Int
	//定义一个byte用来装hash
	var hash [32]byte
	//定义一个计数器并初始化
	nonce := 0
	fmt.Printf("正在挖掘区块：\"%s\"\n", pow.block.Data)
	//计数器不断自增循环
	for nonce < maxNonce {
		//获取拼接的byte并进行hash
		data := pow.prepareData(nonce)
		hash = sha256.Sum256(data)
		//把hash后的数据转为int类型
		hashInt.SetBytes(hash[:])
		//将转换后的hash与目标值比较，如果小于目标值就是-1
		//big.Int 类型比较用cmp方法：https://blog.csdn.net/lying_byr/article/details/94553249
		if hashInt.Cmp(pow.target) == -1 {
			fmt.Printf("\r%x", hash)
			//小于目标值就是有效的
			break
		} else {
			nonce++
		}
	}
	fmt.Println()
	fmt.Println()
	//最后返回计数器的值和结果hash
	return nonce, hash[:]
}

//Validate 用于验证工作量
func (pow *ProofOfWork) Validate() bool {
	//hashInt用来装转换为int型的hash
	var hashInt big.Int

	//这里用已有的nonce进行拼接
	data := pow.prepareData(pow.block.Nonce)
	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])

	//对比大小，是-1就表示验证成功返回true，否则返回false
	isValid := hashInt.Cmp(pow.target) == -1
	return isValid
}
